package jmetal.metaheuristics.singleObjective.panmitic;

import java.util.Comparator;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import jmetal.core.Algorithm;
import jmetal.core.Operator;
import jmetal.core.Problem;
import jmetal.core.Solution;
import jmetal.core.SolutionSet;
import jmetal.util.JMException;
import jmetal.util.comparators.ObjectiveComparator;

public class PanmiticLTGA extends Algorithm {
	private int numFinishedUnits;

	public static int evaluationsTotal;

	/**
	 * 
	 * Constructor Create a new GGA instance.
	 * 
	 * @param problem
	 *            Problem to solve.
	 */
	public PanmiticLTGA(Problem problem) {
		super(problem);
		evaluationsTotal = 0;
	} // GGA

	/**
	 * Execute the GGA algorithm
	 * 
	 * @throws JMException
	 */
	public synchronized SolutionSet execute() throws JMException, ClassNotFoundException {
		int populationSize;
		int maxEvaluations;
		int evaluations;

		SolutionSet population;
		SolutionSet offspringPopulation;

		Operator mutationOperator;
		Operator crossoverOperator;
		Operator selectionOperator;

		Comparator comparator;
		comparator = new ObjectiveComparator(0); // Single objective comparator

		// Read the params
		populationSize = ((Integer) this.getInputParameter("populationSize")).intValue();
		maxEvaluations = ((Integer) this.getInputParameter("maxEvaluations")).intValue();

		// Initialize the variables
		population = new SolutionSet(populationSize);
		offspringPopulation = new SolutionSet(populationSize);

		evaluations = 0;

		// Read the operators
		mutationOperator = this.operators_.get("mutation");
		crossoverOperator = this.operators_.get("crossover");
		selectionOperator = this.operators_.get("selection");

		// Create the initial population
		Solution newIndividual;
		for (int i = 0; i < populationSize; i++) {
			newIndividual = new Solution(problem_);
			problem_.evaluate(newIndividual);
			evaluations++;
			evaluationsTotal++;
			population.add(newIndividual);
		} // for

		// Sort population
		population.sort(comparator);

		ExecutorService executor = Executors.newFixedThreadPool(populationSize / 2);
		List<PanmiticLTGAParallelUnit> units = new Vector<PanmiticLTGAParallelUnit>();
		PanmiticLTGAParallelUnit unit = null;
		while (evaluations < maxEvaluations) {
			// Copy the best two individuals to the offspring population
			offspringPopulation.add(new Solution(population.get(0)));
			offspringPopulation.add(new Solution(population.get(1)));
			units.clear();
			// Reproductive cycle
			for (int i = 0; i < (populationSize / 2 - 1); i++) {
				// Selection
				Solution[] parents = new Solution[2];

				parents[0] = (Solution) selectionOperator.execute(population);
				parents[1] = (Solution) selectionOperator.execute(population);

				unit = new PanmiticLTGAParallelUnit(mutationOperator, crossoverOperator, parents, this);
				units.add(unit);
				executor.execute(unit);
				evaluations += 2;
			}

			while (numFinishedUnits < populationSize / 2 - 1) {
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			
			numFinishedUnits = 0;
			
			for (PanmiticLTGAParallelUnit evaluatedUnit : units) {
				offspringPopulation.add(evaluatedUnit.getOffsprings()[0]);
				offspringPopulation.add(evaluatedUnit.getOffsprings()[1]);
			}

			// The offspring population becomes the new current population
			population.clear();
			for (int i = 0; i < populationSize; i++) {
				population.add(offspringPopulation.get(i));
			}
			offspringPopulation.clear();
			population.sort(comparator);
		} // while

		// Return a population with the best individual
		SolutionSet resultPopulation = new SolutionSet(1);
		resultPopulation.add(population.get(0));
		
		executor.shutdown();
		
		return resultPopulation;
	} 

	public synchronized void notifyUnitFinished() {
		numFinishedUnits++;
		this.notify();
	}

} // gGA